
#include "session.h"

#ifndef CONFIG_MEDIA_EMULATE_ON_PC
#include <arch_irq.h>

#include <asm/i2s.h>
#endif
#include <asm/delay.h>

#include <uapi/stdlib.h>
#include <uapi/string.h>

#ifdef CONFIG_MEDIA_EMULATE_ON_PC
#include <uapi/stdio.h>
#include <uapi/time.h>
#include <SDL2/SDL.h>
#include "ff/ff.h"
// ffplay -ac 2 -ar 44100 -f f32le i2sout.pcm
// f32be f32le f64be f64le u16be u16le u24be u24le u32be u32le u8
#define BLOCK DATBUF_MAX_BUF_LEN
#else
#include <math.h>
#endif

///////////////////////////////////////////////////////////////////////////////////////////////
typedef struct
{
	struct SESSION *session;
	psysbuf_t playbuf[2];
#ifdef CONFIG_MEDIA_EMULATE_ON_PC
	psysbuf_t load_buf;
	long load_size; // file size if file_name is not null
	long load_offset;
	int eof;
	FIL file;
#endif
	audio_info_t info;
	session_speed_t speed;
#ifdef MEDIA_AV_SYNC
	session_av_sync *sync;
#endif
}session_unit_t;

#ifndef CONFIG_MEDIA_EMULATE_ON_PC
#ifdef MEDIA_AV_SYNC
static
int update_pts(session_unit_t *punit, psysbuf_t load_buf, uint32_t count, int type)
{
	punit->sync->a_cur_pkt_pts = load_buf->user.nLowPart;
	punit->sync->a_cur_pkt_num = load_buf->user.nHighPart;
	uint32_t cache_time_ms = 0;
	media_flush_time(&punit->sync->a_play_buf_flush_timestamp, &punit->sync->sys_time);
	int cnt = i2s_out_buffer_cnt();
	if (type == 0) {
		/* if the hardware buffer is empty, and interrupt is not comming */
		punit->sync->a_play_buf_remaining_time = (count * 1000) / punit->sync->a_real_rate * (cnt);
	} else {
		/* if enter after comming interrupt */
		punit->sync->a_play_buf_remaining_time = ((int)count * 1000) / punit->sync->a_real_rate * (cnt - 1);
	}

	return 0;
}
#endif

static inline
int session_buffer_process(session_unit_t *punit, int type)
{
	int valid;
	uint32_t count;
	psysbuf_t load_buf;
	struct SESSION *session;
	if (punit == NULL)
		return -EACCES;
	session = punit->session;
	if (session == NULL)
		return -EFAULT;

	if (punit->playbuf[1] != NULL) {
		return -EBUSY;
	}

	valid = i2s_out_buffer_valid();
	if (valid <= 0)
		return -EINVAL;

	load_buf = SessionBufferPop(session);
	if (load_buf) {

		if (punit->info.samplingsize == 24)
			count = load_buf->size / 6;
		else
			count = load_buf->size / 4;

		if (count < 1) {
			sysbuf_free(load_buf);
			load_buf = NULL;
		}
		else if (punit->playbuf[0] == NULL) { // may be first
			punit->playbuf[0] = load_buf;
		}
		else {
			punit->playbuf[1] = load_buf;
		}
	}

	if (punit->playbuf[0] == NULL
		&& punit->playbuf[1] == NULL) {
		session->state = SSTATE_STOP;
		return 0;
	}

	if (load_buf) {
#ifdef MEDIA_AV_SYNC
		update_pts(punit, load_buf, count, type);
#endif
		i2s_out_addbuffer(load_buf->haddr + load_buf->offset, count);
	}

	if (punit->playbuf[1] == NULL)
		session->state = SSTATE_RUNNING_IDLE;
	else
		session->state = SSTATE_RUNNING;

}

static void i2s_out_irq_handler(session_unit_t *punit)
{
	psysbuf_t load_buf;
	struct SESSION *session = punit->session;

	if (i2s_out_in_irq())
	{
		load_buf = punit->playbuf[0];
		if (load_buf) {
			sysbuf_free(load_buf);
		}
		punit->playbuf[0] = punit->playbuf[1];
		punit->playbuf[1] = NULL;

		if (session->state > SSTATE_INITED
			&& session->state != SSTATE_RUNNING_PAUSE)
		{
			session_buffer_process(punit, 1);
		}

		i2s_out_irq_clear();
	}

	arch_irq_clear(IRQ_I2S_OUT);
}

static int i2s_out_noirq_running(session_unit_t *punit)
{
	struct SESSION *session;
	if (punit == NULL)
		return -EACCES;
	session = punit->session;
	if (session == NULL)
		return -EFAULT;

	if (session->state == SSTATE_RUNNING_IDLE
		|| session->state == SSTATE_STOP)
	{
		uint32_t flags;
		local_irq_save(flags);

		session_buffer_process(punit, 0);

		local_irq_restore(flags);

	}

}
#endif
#ifdef CONFIG_MEDIA_EMULATE_ON_PC
////////////////////////////////////////////////////////////////////////////////
/*!
 * \brief Callback function of audio play, SDL2 lib will call the function when it need more audio frame data
 */
static Uint8 *audio_pos;
static Uint32 audio_len = 0;
static Uint32 audio_len_origin = 0;
static session_unit_t *gpunit = NULL;
//static FILE *outfile;
static void read_audio_data_cb(void *udata, Uint8 *stream, int len)
{
    struct timeval tv;

	SDL_memset(stream, 0, len);

	if (!audio_len) {
		return;
	}

	len = (len > audio_len ? audio_len : len);

	// copy from 'audio_pos' to 'stream' with 'len'
	SDL_MixAudio(stream, audio_pos, len, SDL_MIX_MAXVOLUME);

//	fwrite(audio_pos, 1, len, outfile);

	if (audio_len == audio_len_origin) {
		int fix_delay = 100; // fix the not sync in play
#ifdef MEDIA_AV_SYNC
		media_flush_time(&gpunit->sync->a_play_buf_flush_timestamp, &gpunit->sync->sys_time);
		gpunit->sync->a_play_buf_remaining_time = (audio_len_origin / 8 * 1000) / gpunit->sync->a_real_rate + fix_delay;
#endif
	}

	audio_pos += len;
	audio_len -= len;

	if (audio_len <= 0) { // play one frame finish
		gettimeofday(&tv, NULL);
//		av_sync->atime = (tv.tv_sec - av_sync->start_time) * 1000 + tv.tv_usec / 1000;
//		av_sync->aclock = (float)gpts / (float)gtime_scale;
		if (gpunit->load_buf)
			sysbuf_free(gpunit->load_buf);
	}
}

static
int sdl_play_start(int channel, int sample_rate, int sample_size, int samples)
{
	SDL_AudioSpec spec;

	if (SDL_Init(SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
		debug("Could not initialize SDL - %s\n", SDL_GetError());
		return -1;
	}

	memset(&spec, 0, sizeof(spec));
	if (sample_rate)
		spec.freq = sample_rate;
	else
		spec.freq = 44100;  // sample frequency(samples per second)

	// f32be f32le f64be f64le u16be u16le u24be u24le u32be u32le u8
	if (sample_size == 16)
		spec.format = AUDIO_S16SYS;
	else //TODO: add other type
		spec.format = AUDIO_F32SYS; // ffavcodec audio data format, SDL_AudioFormat

	if (channel)
		spec.channels = channel;
	else
		spec.channels = 1;  // number of sound channels, Uint8

	spec.silence = 0;
	spec.samples = samples; // mp3: 1152, aac: 1024;
	spec.callback = read_audio_data_cb;
	spec.userdata = NULL;

	debug("Audio init, sample_rate: %d, format: 0x%04x, channels: %d\n",
			spec.freq, spec.format, spec.channels);

	if (SDL_OpenAudio(&spec, NULL) < 0) {
		debug("Can't open audio play device!\n");
		return -1;
	}

	/* Play start */
	SDL_PauseAudio(0);

	return 0;
}

static
long SESSIONAPI(StreamLoad)(session_unit_t *punit)
{
	long l2 = 0;
	psysbuf_t load_buf;

	load_buf = punit->load_buf;
	if (load_buf == NULL) {
		load_buf = sysbuf_alloc(SYSBUF_GROUP_DATBUFS);
		if (load_buf == NULL)
			return -1;
		punit->load_buf = load_buf;
	}

	/* get frame from file */
	if(punit->load_size > 0)
	{
		l2 = BLOCK;
		if (l2 >= punit->load_size) {
			l2 = punit->load_size;
			load_buf->flags |= SYS_BUF_FLAG_EOF;
		}

		f_read(&(punit->file), (void*)HWADDR(load_buf->haddr), l2, NULL);
		load_buf->size = l2;		// read length
		punit->load_offset += l2;	// file offset
		punit->load_size -= l2; 	// file length
	}
	else
	{
		/* get frame from buffer */
		psysbuf_t buf = SessionBufferPop(punit->session);
		if (buf) {
			if (buf->list.group == load_buf->list.group) {
				load_buf = buf;
			} else {
				phys_addr_t src, dst;
				src = buf->haddr + buf->offset;
				dst = load_buf->haddr + load_buf->offset;
				memcpy((void*)dst, (void*)src, buf->size);
				load_buf->size = buf->size;
				if (buf->flags & SYS_BUF_FLAG_EOF)
					load_buf->flags |= SYS_BUF_FLAG_EOF;
				load_buf->user = buf->user;
				sysbuf_free(buf);
			}
		} else {
			load_buf = NULL;
		}
	}

	if (load_buf == NULL)
		return 0;

	if (load_buf->flags & SYS_BUF_FLAG_EOF) {
		punit->eof = 1;
	}
	if (load_buf) {
#ifdef MEDIA_AV_SYNC
		punit->sync->a_cur_pkt_pts = load_buf->user.nLowPart;
#endif
		audio_pos = (Uint8 *)(load_buf->haddr);
		audio_len = load_buf->size;
		audio_len_origin = load_buf->size;
	}

	// free new buffer
	if (load_buf != punit->load_buf) {
		sysbuf_free(load_buf);
	}

	return load_buf->size;
}
#endif

static
SESSIONSTATE SESSIONAPI(SessionRun)(struct SESSION *session)
{
	session_unit_t *punit = NULL;
	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (punit == NULL)
		return SSTATE_NULL;

#ifndef CONFIG_MEDIA_EMULATE_ON_PC
#ifdef MEDIA_AV_SYNC
	if (punit->playbuf[0] == NULL)
		i2s_out_noirq_running(punit);
#else
	i2s_out_noirq_running(punit);
#endif
#endif

#ifdef CONFIG_MEDIA_EMULATE_ON_PC
	if (audio_len <= 0)
		SESSIONAPI(StreamLoad)(punit);
#endif
	return session->state;
}

#ifdef MEDIA_AV_SYNC
static
int update_time(session_unit_t *punit, session_file_t *file, long real_rate)
{
	punit->sync = file->sync;
	punit->sync->a_real_rate = real_rate;
	punit->sync->a_play_buf_flush_timestamp.last_tick = xthal_get_ccount();
	punit->sync->a_play_buf_flush_timestamp.last_second = 0;
	punit->sync->a_play_buf_flush_timestamp.last_ms = 0;

	return 0;
}
#endif

static
int SESSIONAPI(SSCMD_STREAM_START)(struct SESSION *session, session_file_t *file)
{
	uint32_t flags;
	session_unit_t *punit = NULL;

	debug("Session '%s' command 'SSCMD_STREAM_START'\n", session->name);

	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (punit == NULL)
		return -EINVAL;
#ifndef CONFIG_MEDIA_EMULATE_ON_PC
	if (session->state < SSTATE_INITED)
		return -EACCES;
#else
	gpunit = punit;
#endif
	session->state = SSTATE_INITED;

	// reset buffer
	SessionBufferDeInit(session);

	if (punit->playbuf[0])
		sysbuf_free(punit->playbuf[0]);
	punit->playbuf[0] = NULL;
	if (punit->playbuf[1])
		sysbuf_free(punit->playbuf[1]);
	punit->playbuf[1] = NULL;

#ifdef MEDIA_AV_SYNC
	punit->sync = file->sync;
#endif
#ifndef CONFIG_MEDIA_EMULATE_ON_PC
	// hardware initial
	local_irq_save(flags);

	i2s_out_setspd(punit->speed);

	long real_rate = i2s_out_set_samplingrate(punit->info.samplingrate);
#ifdef MEDIA_AV_SYNC
	update_time(punit, file, real_rate);
#endif
	i2s_out_enable_24bit(punit->info.samplingsize == 24 ? 1 : 0);

	local_irq_restore(flags);

	// enable interrupts
	i2s_out_irq_clear();
	arch_irq_clear(IRQ_I2S_OUT);
	arch_irq_enable(IRQ_I2S_OUT);

	// clear data & mark

	session->state = SSTATE_STOP;
#endif

#ifdef CONFIG_MEDIA_EMULATE_ON_PC

	session->state = SSTATE_RUNNING;

	if (file->filename) {
#ifdef MEDIA_AV_SYNC
		gpunit->sync->a_real_rate = file->ta.sample_rate;
#endif
		debug("the PCM file name: \"%s\"\n", file->filename);
		if(f_open(&(punit->file), file->filename, FA_READ)!= FR_OK)
		{
			debug("Can't open the PCM file: %s!\n", file->filename);
			return -EBADF;
		}
		else
		{
			punit->load_size = f_size(&(punit->file));
			debug("stream file size is %ld \n", punit->load_size);
		}
	}
	if (file->type == SST_MP3)
		sdl_play_start(file->ta.channel, file->ta.sample_rate, file->ta.sample_size, 1152); // frame->nb_samples == 1152
	else
		sdl_play_start(file->ta.channel, file->ta.sample_rate, file->ta.sample_size, 1024);

//	outfile = fopen("i2sout.pcm", "wb");
#endif
	return 0;
}

static
int SESSIONAPI(SSCMD_STREAM_PAUSE)(struct SESSION *session)
{
	debug("Session '%s' command 'SSCMD_STREAM_PAUSE'\n", session->name);
#ifndef CONFIG_MEDIA_EMULATE_ON_PC
	if (session->state == SSTATE_RUNNING
		|| session->state == SSTATE_RUNNING_IDLE)
	{
		uint32_t flags;
		session->state = SSTATE_RUNNING_PAUSE;

		local_irq_save(flags);
		i2s_out_pause();
		local_irq_restore(flags);

	}
	else {
		return -EPERM;
	}
#endif
	return 0;
}

static
int SESSIONAPI(SSCMD_STREAM_RESUME)(struct SESSION *session)
{
	debug("Session '%s' command 'SSCMD_STREAM_RESUME'\n", session->name);
#ifndef CONFIG_MEDIA_EMULATE_ON_PC
	if (session->state == SSTATE_RUNNING_PAUSE)
	{
		uint32_t flags;

		local_irq_save(flags);
		i2s_out_resume();
		local_irq_restore(flags);

		session->state = SSTATE_RUNNING;
	}
	else {
		return -EPERM;
	}
#endif
	return 0;
}

#ifdef MEDIA_AV_SYNC
static
int SESSIONAPI(SSCMD_STREAM_SKIP)(struct SESSION *session)
{
	session_unit_t *punit = NULL;
	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (punit == NULL)
		return -EACCES;
#ifndef CONFIG_MEDIA_EMULATE_ON_PC
	/* clear i2sout buffer after skip the audio(fast forward or fast rewind) */

#ifdef MEDIA_SKIP_NOT_HW_RESET
	// clear i2sout buffer, bit2 of I2S_CN can clear i2sout buffer
	u32_t valcn;
	hwio_readl_shdev(0, valcn.dat, DGUS_ABS_APPI_ADDR + DGUS_SB_APPI_I2S_OUT_CN_OFFSET);
	valcn.dat[0] &= ~(1 << 2); // stop play audio(can clear the i2sout buffer)
	hwio_writel_shdev(0, valcn.dat, DGUS_ABS_APPI_ADDR + DGUS_SB_APPI_I2S_OUT_CN_OFFSET);
	hwio_readl_shdev(0, valcn.dat, DGUS_ABS_APPI_ADDR + DGUS_SB_APPI_I2S_OUT_CN_OFFSET);
	valcn.dat[0] |= (1 << 2); // start play audio(can clear the i2sout buffer)
	hwio_writel_shdev(0, valcn.dat, DGUS_ABS_APPI_ADDR + DGUS_SB_APPI_I2S_OUT_CN_OFFSET);

	// reset session buffer
	SessionBufferDeInit(session);
#else
	i2s_out_swrst();
	i2s_out_stop();

	// stop irq
	arch_irq_disable(IRQ_I2S_OUT);
	i2s_out_irq_clear();
	arch_irq_clear(IRQ_I2S_OUT);

	// free & reset buffer
	SessionBufferDeInit(session);
	if (punit->playbuf[0])
		sysbuf_free(punit->playbuf[0]);
	punit->playbuf[0] = NULL;
	if (punit->playbuf[1])
		sysbuf_free(punit->playbuf[1]);
	punit->playbuf[1] = NULL;

#	if 0
	// close hardware
	i2s_out_disable();
	arch_irq_free(IRQ_I2S_OUT);

	// install interrupts
	i2s_out_irq.handler = (irq_handler_t)(&i2s_out_irq_handler);
	i2s_out_irq.params = (void*)punit;
	arch_irq_request(IRQ_I2S_OUT, &i2s_out_irq);
	SessionBufferInit(session);
#	endif

	// hardware initial
	i2s_out_setspd(punit->speed);
	// estimate the time resume in i2s buffer with real play rate
	i2s_out_set_samplingrate(punit->info.samplingrate);
	i2s_out_enable_24bit(punit->info.samplingsize == 24 ? 1 : 0);
	// enable interrupts
	arch_irq_enable(IRQ_I2S_OUT);
#endif /* MEDIA_SKIP_NOT_HW_RESET */
#endif
	return 0;
}
#endif

static
int SESSIONAPI(SSCMD_STREAM_STOP)(struct SESSION *session)
{
	uint32_t flags;

	session_unit_t *punit = NULL;

	debug("Session '%s' command  'SSCMD_STREAM_STOP'\n", session->name);

	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (punit == NULL)
		return -EINVAL;

	// set state
	session->state = SSTATE_INITED;
#ifndef CONFIG_MEDIA_EMULATE_ON_PC
	/*
	 * Software reset may work well for version:
	 * 		HARDWARE_VERSION_20181205
	 * 		HARDWARE_VERSION_20191209
	 * Force to reset
	 */
	local_irq_save(flags);

	// stop hardware
	i2s_out_stop();

	// software reset
	i2s_out_swrst();

	local_irq_restore(flags);

	// stop irq
	arch_irq_disable(IRQ_I2S_OUT);
	i2s_out_irq_clear();
	arch_irq_clear(IRQ_I2S_OUT);
#endif
	// free & reset buffer
	SessionBufferDeInit(session);

	if (punit->playbuf[0])
		sysbuf_free(punit->playbuf[0]);
	punit->playbuf[0] = NULL;
	if (punit->playbuf[1])
		sysbuf_free(punit->playbuf[1]);
	punit->playbuf[1] = NULL;
#ifdef CONFIG_MEDIA_EMULATE_ON_PC
	SDL_CloseAudio();
	SDL_Quit();
#endif
	return 0;
}

static
int SESSIONAPI(SSCMD_STREAM_BUFCHANGED)(struct SESSION *session)
{
	session_unit_t *punit = NULL;
	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (punit == NULL)
		return -EINVAL;

#ifndef CONFIG_MEDIA_EMULATE_ON_PC
#ifdef MEDIA_AV_SYNC
	if (punit->playbuf[0] == NULL)
		i2s_out_noirq_running(punit);
#else
	i2s_out_noirq_running(punit);
#endif
#endif

	return 0;
}

static
int SESSIONAPI(SSCMD_SET_AUDIO_INFO)(struct SESSION *session, audio_info_t *pinfo)
{
	uint32_t flags;
	session_unit_t *punit = NULL;
	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (punit == NULL)
		return -EACCES;

#ifndef CONFIG_MEDIA_EMULATE_ON_PC
	debug("Session '%s' command 'SSCMD_SET_AUDIO_INFO (0x%x,0x%x)'\n",
		session->name, pinfo->samplingsize, pinfo->samplingrate);

	punit->info.samplingsize = pinfo->samplingsize;
	punit->info.samplingrate = pinfo->samplingrate;

	local_irq_save(flags);

	i2s_out_set_samplingrate(punit->info.samplingrate);
	i2s_out_enable_24bit(punit->info.samplingsize == 24 ? 1 : 0);

	local_irq_restore(flags);
#endif
	return 0;
}

static
int SESSIONAPI(SSCMD_STREAM_SETSPEED)(struct SESSION *session, session_speed_t *ps)
{
	uint32_t flags;
	session_unit_t *punit = NULL;
	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (punit == NULL)
		return -EACCES;

	debug("Session '%s' command 'SSCMD_STREAM_SETSPEED' x%d\n", session->name, *ps);
#ifndef CONFIG_MEDIA_EMULATE_ON_PC
	punit->speed = *ps;
#ifdef MEDIA_AV_SYNC
	punit->sync->speed = punit->speed;
#endif

	local_irq_save(flags);

	i2s_out_setspd(punit->speed);

	local_irq_restore(flags);
#endif
	return 0;
}

static
int SESSIONAPI(SSCMD_STREAM_GETSPEED)(struct SESSION *session, session_speed_t *ps)
{
	session_unit_t *punit = NULL;

	debug("Session '%s' command 'SSCMD_STREAM_GETSPEED'\n", session->name);

	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (punit == NULL)
		return -EACCES;

	*ps = punit->speed;

	return 0;
}

static
int SESSIONAPI(SSCMD_STREAM_GETVALIDINSIZE)(struct SESSION *session, uint32_t *psz)
{
	session_unit_t *punit = NULL;
	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (punit == NULL)
		return -EACCES;
#ifndef CONFIG_MEDIA_EMULATE_ON_PC
	if (psz) {
		if (punit->info.samplingsize == 24)
			*psz = 6 * punit->info.samplingrate;
		else
			*psz = 4 * punit->info.samplingrate;
	}
#endif
	return 0;
}

static
int SESSIONAPI(SessionCommand)(struct SESSION *session, int cmd, void *params)
{
	int ret = 0;
	session_unit_t *punit = NULL;
	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (session == NULL || punit == NULL)
		return -EINVAL;

	switch (cmd)
	{
	case SSCMD_STREAM_START:
	{
		return SESSIONAPI(SSCMD_STREAM_START)(session, (session_file_t *)params);
	}break;
	case SSCMD_STREAM_PAUSE:
	{
		return SESSIONAPI(SSCMD_STREAM_PAUSE)(session);
	}break;
	case SSCMD_STREAM_RESUME:
	{
		return SESSIONAPI(SSCMD_STREAM_RESUME)(session);
	}break;
	case SSCMD_STREAM_SKIP:
	{
#ifdef MEDIA_AV_SYNC
		return SESSIONAPI(SSCMD_STREAM_SKIP)(session);
#endif
	}break;
	case SSCMD_STREAM_STOP:
	{
		return SESSIONAPI(SSCMD_STREAM_STOP)(session);
	}break;
	case SSCMD_STREAM_BUFCHANGED:
	{
		return SESSIONAPI(SSCMD_STREAM_BUFCHANGED)(session);
	}break;
	case SSCMD_SET_AUDIO_INFO:
	{
		return SESSIONAPI(SSCMD_SET_AUDIO_INFO)(session, (audio_info_t*)params);
	}break;
	case SSCMD_STREAM_SETSPEED:
	{
		return SESSIONAPI(SSCMD_STREAM_SETSPEED)(session, (session_speed_t*)params);
	}break;
	case SSCMD_STREAM_GETSPEED:
	{
		return SESSIONAPI(SSCMD_STREAM_GETSPEED)(session, (session_speed_t*)params);
	}break;
	case SSCMD_STREAM_GETVALIDINSIZE:
	{
		return SESSIONAPI(SSCMD_STREAM_GETVALIDINSIZE)(session, (uint32_t *)params);
	}break;
	default:
	{
		debug("Session '%s' command %d unknown !\n", session->name, cmd);
		ret = -EINVAL;
	};
	};

	return ret;
}

static
void SESSIONAPI(SessionDeInit)(struct SESSION *session)
{
	session_unit_t *punit = NULL;
	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (punit != NULL)
		free(punit);

	debug("SessionDeInit : %s ..\n", session->name);
#ifndef CONFIG_MEDIA_EMULATE_ON_PC
	// close hardware
	arch_irq_disable(IRQ_I2S_OUT);
	i2s_out_disable();
	arch_irq_clear(IRQ_I2S_OUT);
	arch_irq_free(IRQ_I2S_OUT);
#endif
	session->handle = NULL;
}
#ifndef CONFIG_MEDIA_EMULATE_ON_PC
static irq_desc i2s_out_irq;
#endif
static
SESSIONHANDLE SESSIONAPI(SessionInit)(struct SESSION *session)
{
	uint32_t flags;
	session_unit_t *punit = (session_unit_t*)malloc(sizeof(session_unit_t) + 64);
	if (punit == NULL)
		return NULL;

	debug("SessionInit : %s ..\n", session->name);

	memset(punit, 0, sizeof(session_unit_t));
#ifndef CONFIG_MEDIA_EMULATE_ON_PC
	punit->speed = SESSION_SPEED_X1;
	punit->info.samplingsize = 16;
	punit->info.samplingrate = 44100;
#endif
	punit->session = session;
	session->SessionRun = &SESSIONAPI(SessionRun);
	session->SessionCommand = &SESSIONAPI(SessionCommand);
	session->SessionDeInit = &SESSIONAPI(SessionDeInit);
	session->state = SSTATE_INITED;

#ifndef CONFIG_MEDIA_EMULATE_ON_PC
	// enable & reset hardware
	local_irq_save(flags);

	i2s_out_swrst();

	local_irq_restore(flags);

	// install interrupts
	arch_irq_disable(IRQ_I2S_OUT);
	arch_irq_clear(IRQ_I2S_OUT);

	i2s_out_irq.handler = (irq_handler_t)(&i2s_out_irq_handler);
	i2s_out_irq.params = (void*)punit;

	arch_irq_request(IRQ_I2S_OUT, &i2s_out_irq);
#endif
	SessionBufferInit(session);

	session->handle = (SESSIONHANDLE)punit;
	return session->handle;
}

struct SESSION gSession_i2sout =
{ "i2sout", (1 << SST_I2SOUT),
&SESSIONAPI(SessionInit),
};
